<template>
  <el-teleport :disabled="!teleported" :to="appendTo">
    <transition
      v-if="shouldRender || !ariaHidden"
      :name="transitionClass"
      :appear="!persistentRef"
      @after-leave="onTransitionLeave"
      @before-enter="onBeforeEnter"
      @after-enter="onAfterShow"
      @before-leave="onBeforeLeave"
    >
      <el-popper-content
        v-show="shouldShow"
        :id="id"
        ref="contentRef"
        v-bind="$attrs"
        :aria-label="ariaLabel"
        :aria-hidden="ariaHidden"
        :boundaries-padding="boundariesPadding"
        :fallback-placements="fallbackPlacements"
        :gpu-acceleration="gpuAcceleration"
        :offset="offset"
        :placement="placement"
        :popper-options="popperOptions"
        :arrow-offset="arrowOffset"
        :strategy="strategy"
        :effect="effect"
        :enterable="enterable"
        :pure="pure"
        :popper-class="popperClass"
        :popper-style="[popperStyle!, contentStyle]"
        :reference-el="referenceEl"
        :trigger-target-el="triggerTargetEl"
        :visible="shouldShow"
        :z-index="zIndex"
        :loop="loop"
        @mouseenter="onContentEnter"
        @mouseleave="onContentLeave"
        @blur="onBlur"
        @close="onClose"
      >
        <slot />
      </el-popper-content>
    </transition>
  </el-teleport>
</template>

<script lang="ts" setup>
import { computed, inject, onBeforeUnmount, ref, unref, watch } from 'vue'
import { computedEager, onClickOutside } from '@vueuse/core'
import { useNamespace, usePopperContainerId } from '@element-plus/hooks'
import {
  castArray,
  composeEventHandlers,
  focusElement,
} from '@element-plus/utils'
import { ElPopperContent } from '@element-plus/components/popper'
import ElTeleport from '@element-plus/components/teleport'
import { TOOLTIP_INJECTION_KEY } from './constants'
import { useTooltipContentProps } from './content'
import { isTriggerType } from './utils'

import type { PopperContentInstance } from '@element-plus/components/popper'

defineOptions({
  name: 'ElTooltipContent',
  inheritAttrs: false,
})

const props = defineProps(useTooltipContentProps)

const { selector } = usePopperContainerId()
const ns = useNamespace('tooltip')

const contentRef = ref<PopperContentInstance>()
const popperContentRef = computedEager(() => contentRef.value?.popperContentRef)
let stopHandle: ReturnType<typeof onClickOutside>
const {
  controlled,
  id,
  open,
  trigger,
  onClose,
  onOpen,
  onShow,
  onHide,
  onBeforeShow,
  onBeforeHide,
} = inject(TOOLTIP_INJECTION_KEY, undefined)!
const transitionClass = computed(() => {
  return props.transition || `${ns.namespace.value}-fade-in-linear`
})
const persistentRef = computed(() => {
  // For testing, we would always want the content to be rendered
  // to the DOM, so we need to return true here.
  if (typeof process !== 'undefined') {
    if (
      process.env.NODE_ENV === 'test' &&
      !process.env.RUN_TEST_WITH_PERSISTENT
    ) {
      return true
    }
  }
  return props.persistent
})

onBeforeUnmount(() => {
  stopHandle?.()
})

const shouldRender = computed(() => {
  return unref(persistentRef) ? true : unref(open)
})

const shouldShow = computed(() => {
  return props.disabled ? false : unref(open)
})

const appendTo = computed(() => {
  return props.appendTo || selector.value
})

const contentStyle = computed(() => props.style ?? {})

const ariaHidden = ref(true)

const onTransitionLeave = () => {
  onHide()
  isFocusInsideContent() && focusElement(document.body, { preventScroll: true })
  ariaHidden.value = true
}

const stopWhenControlled = () => {
  if (unref(controlled)) return true
}

const onContentEnter = composeEventHandlers(stopWhenControlled, () => {
  if (props.enterable && isTriggerType(unref(trigger), 'hover')) {
    onOpen()
  }
})

const onContentLeave = composeEventHandlers(stopWhenControlled, () => {
  if (isTriggerType(unref(trigger), 'hover')) {
    onClose()
  }
})

const onBeforeEnter = () => {
  contentRef.value?.updatePopper?.()
  onBeforeShow?.()
}

const onBeforeLeave = () => {
  onBeforeHide?.()
}

const onAfterShow = () => {
  onShow()
}

const onBlur = () => {
  if (!props.virtualTriggering) {
    onClose()
  }
}

const isFocusInsideContent = (event?: FocusEvent) => {
  const popperContent: HTMLElement | undefined =
    contentRef.value?.popperContentRef
  const activeElement = (event?.relatedTarget as Node) || document.activeElement

  return popperContent?.contains(activeElement)
}

watch(
  () => unref(open),
  (val) => {
    if (!val) {
      stopHandle?.()
    } else {
      ariaHidden.value = false
      stopHandle = onClickOutside(
        popperContentRef,
        () => {
          if (unref(controlled)) return
          const needClose = castArray(unref(trigger)).every((item) => {
            return item !== 'hover' && item !== 'focus'
          })
          if (needClose) {
            onClose()
          }
        },
        { detectIframe: true }
      )
    }
  },
  {
    flush: 'post',
  }
)

watch(
  () => props.content,
  () => {
    contentRef.value?.updatePopper?.()
  }
)

defineExpose({
  /**
   * @description el-popper-content component instance
   */
  contentRef,
  /**
   * @description validate current focus event is trigger inside el-popper-content
   */
  isFocusInsideContent,
})
</script>
